﻿//************************************************************************************************************
// CoNatural.Data.dll
// Author: Roger Torres
//************************************************************************************************************
// You can use, modify, and distribute the source code and executable programs based on the source code.
// This source code is provided "as is" and without warranties as to performance or merchantability. 
// The author and/or distributors of this source code may have made statements about this source code. 
// Any such statements do not constitute warranties and shall not be relied on by the user in deciding 
// whether to use this source code. This source code is provided without any express or implied warranties 
// whatsoever. Because of the diversity of conditions and hardware under which this source code may be used, 
// no warranty of fitness for a particular purpose is offered. The user is advised to test the source code 
// thoroughly before relying on it. The user must assume the entire risk of using the source code.
//************************************************************************************************************

using System;
using System.Data;
using System.Reflection;
using System.Collections.Generic;

namespace CoNatural.Data {
	public class Parameter {
      /// <summary>
      /// Used internally to initialize commands with parameters
      /// </summary>
      /// <param name="name">The parameter name.</param>
		/// <param name="type">The CLR type.</param>
      /// <param name="parameterAttribute">The extra parameter properties.</param>
		internal protected Parameter(string name, Type type, ParameterAttribute parameterAttribute) {
			Name = name;
			Type = type;

         if (parameterAttribute == null) {
            Direction = ParameterDirection.Input;
            Size = -1; // for varchar(MAX), varbinary(MAX)
            Precision = 0;
            Scale = 0;
         }
         else {
            Direction = parameterAttribute.Direction;
				Size = parameterAttribute.Size;
            Precision = parameterAttribute.Precision;
            Scale = parameterAttribute.Scale;
         }

			MapDbType(parameterAttribute);
		}

		/// <summary>
		/// This works fine for standard ADO.NET DbType
		/// </summary>
		protected virtual void MapDbType(ParameterAttribute parameterAttribute) {
			Type type = Type;

			// handle nullable types
			if (type.IsNullable()) {
				IsNullable = true;
				DbType = (DbType)Enum.Parse(typeof(DbType), type.GetGenericArguments()[0].Name);
			}
			// handle binary types
			else if (type.IsArray && type.Equals(typeof(byte[]))) {
				IsNullable = true;
				DbType = DbType.Binary;
			}
			// handle strings
			else if(type.Equals(typeof(string))) {
				IsNullable = parameterAttribute == null ? false : parameterAttribute.IsNullableString;
				DbType = System.Data.DbType.String;
			}
			// handle other types 
			else {
				IsNullable = false;
				DbType = (DbType)Enum.Parse(typeof(DbType), type.Name);
			}

			// by default is null
			UdtName = null;
		}

		public string Name { get; protected set; }
		public Type Type { get; protected set; }
		public bool IsNullable { get; protected set; }
		public DbType DbType { get; protected set; }
		public ParameterDirection Direction { get; protected set; }
		public int Size { get; protected set; }
		public byte Precision { get; protected set; }
		public byte Scale { get; protected set; }
		public string UdtName { get; protected set; }

		// for batch linking
		internal protected int BatchIndex { get; set; }
		internal protected Parameter BatchLink { get; set; }
		internal protected ICommand BatchCommand { get; set; }
		internal protected PropertyInfo PropertyInfo { get; private set; }
		internal protected object Value {
			get {
				object value = PropertyInfo.GetValue(BatchCommand, null);
				if(value != null) {
					// trim string properties to max size
					if(DbType == DbType.String) {
						if(Size > 0) {
							string str = (string)value;
							value = str.Length > Size ? str.Substring(0, Size) : str;
						}
					}
					// trim binary properties to max size
					else if(DbType == DbType.Binary) {
						if(Size > 0) {
							byte[] bin = (byte[])value;
							if(bin.Length > Size) {
								byte[] copy = new byte[Size];
								Array.Copy(bin, copy, Size);
								value = copy;
							}
						}
					}
				}
				return value ?? DBNull.Value;
			}
			set {
				PropertyInfo.SetValue(BatchCommand, value == DBNull.Value ? null : value, null);
			}
		}

		/// <summary>
		/// Sets parameter value following rules
		/// </summary>
		/// <param name="parameter">The parameter</param>
		/// <param name="value">The value</param>
		static public void SetValue(IDbDataParameter parameter, object value) {
			if(value != null) {
				// trim string properties to max size
				if(parameter.DbType == DbType.String) {
					if(parameter.Size > 0) {
						string str = (string)value;
						value = str.Length > parameter.Size ? str.Substring(0, parameter.Size) : str;
					}
				}
				// trim binary properties to max size
				else if(parameter.DbType == DbType.Binary) {
					if(parameter.Size > 0) {
						byte[] bin = (byte[])value;
						if(bin.Length > parameter.Size) {
							byte[] copy = new byte[parameter.Size];
							Array.Copy(bin, copy, parameter.Size);
							value = copy;
						}
					}
				}
			}
			parameter.Value = value ?? DBNull.Value;
		}

		/// <summary>
		/// Gets parameter value following rules
		/// </summary>
		/// <param name="parameter">The parameter</param>
		/// <returns>The value</returns>
		static public object GetValue(IDbDataParameter parameter) {
			return parameter.Value == DBNull.Value ? null : parameter.Value;
		}

		/// <summary>
		/// Enumerates parameters that map to public properties of command.
		/// </summary>
		/// <param name="dbProvider">The db provider.</param>
		/// <param name="commandType">The command type.</param>
		/// <returns>The parameters.</returns>
		static public IEnumerable<Parameter> GetParameters(IDbProvider dbProvider, Type commandType) {
			foreach(var p in commandType.GetProperties()) {
				// skip properties with IgnoreParameterAttribute
				object[] attributes = p.GetCustomAttributes(typeof(IgnoreParameterAttribute), false);
				if(attributes.Length > 0)
					continue;

				// check if property is decorated with ParameterAttribute for extra settings
				ParameterAttribute pAttr = null;
				attributes = p.GetCustomAttributes(typeof(ParameterAttribute), false);
				if(attributes.Length > 0)
					pAttr = (ParameterAttribute)attributes[0];

				Parameter pb = dbProvider.CreateParameter(p.Name, p.PropertyType, pAttr);
				pb.PropertyInfo = p;
				yield return pb;
			}
			yield break;
		}
	}
}
